import json
import traceback
from collections import OrderedDict
from collections import deque


def _setup_logger():
    log_format = logging.Formatter("[%(asctime)s %(levelname)s] %(message)s")
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)

    console_handler = logging.StreamHandler()
    console_handler.setFormatter(log_format)
    logger.handlers = [console_handler]

    return logger


logger = _setup_logger()

def ensure_dir(dirname):
    """确保生成目录"""
    dirname = Path(dirname)
    if not dirname.is_dir():
        dirname.mkdir(parents=True, exist_ok=False)


def read_json(fname):
    """.json->对象"""
    fname = Path(fname)
    with fname.open('rt') as handle:
        return json.load(handle, object_hook=OrderedDict)


def write_json(content, fname):
    """对象->.json"""
    fname = Path(fname)
    with fname.open('wt') as handle:
        json.dump(content, handle, indent=4, sort_keys=False)


class AppendToJson:
    """将抽取得到的Json格式的知识三元组添加写入Json文件
    功能：将knowledge字典写入file_path的json文件
    """

    def append(self, file_path, knowledge):
        """添加到Json文件
        Args:
            file_path: string，Json文件路径
            knowledge: dict，抽取出的知识
        Returns:
        """
        with open(file_path, 'a', encoding='utf-8') as f_out:
            try:
                f_out.write(json.dumps(knowledge, ensure_ascii=False))
                f_out.write('\n')
                f_out.flush()
            except Exception as e:
                traceback.print_exc()
                raise e
            finally:
                f_out.close()

def json_test():
    from pathlib import Path
    output_path = Path('./data/triple.json')
    print(output_path)
    triple = dict()
    triple['编号'] = 1
    AppendToJson().append(output_path, triple)


import json

from dict_deal import dict2DataFrame
from list_rel.list_deal import list_dict_count


def json_load_path(dirname:str,filename:str):
    """
    参数：dir:上级目录下的目录名；filename:文件名
    功能：.json->对象；读取json文件为对象
    调用：
    dirname='data\list_dict_file'
    filename='eval_test.txt.json_forward_model_last.mdl.json'
    ld=json_load_path(dirname,filename)
    print(len(ld), type(ld), ld[0])  # 302345 <class 'list'>
    """
    import os
    from pathlib import Path
    abs_dir_par = Path(__file__).parent.parent.absolute()##取当前目录的上级目录
    dir_path=os.path.join(abs_dir_par,dirname,filename)
    print("加载json文件：",dir_path)
    with open(dir_path,'r',encoding='utf-8') as fin:
        ld = json.load(fin)
    return ld


def json_list_dict_count(dirname,filename,num=10,typename=1)->{}:
    """
    输入形式：json中的[{}]
    功能：json_list_dict计数(取前n项)-调用json_load_path和list_dict_count
    参数：type=1  list中带括号的；（type=0  list中不带括号的）
    返回：count_true,count_false
    返回形式：{键:数量值}
    调用：
    dirname='data\list_dict_file'
    filename='eval_test.txt.json_forward_model_last.mdl.json'#302345 -[{}]
    count_true,count_false=json_list_dict_count(dirname,filename)
    print(f"count_true:{count_true}")
    print(f"count_false:{count_false}")
    """
    ld=json_load_path(dirname,filename)#[{}]
    # print(f"{len(ld)},{type(ld)}包含类型 {type(ld[0])}")  # 302345 <class 'list'>
    count_true,count_false=list_dict_count(ld,typename=typename)
    count_true = count_true.most_common(num) # Counter取前n项-返回 [(键,数量值),]
    count_true=dict(count_true)#返回{键:数量值}
    count_false = count_false.most_common(num) # Counter取前n项-返回 [(键,数量值),]
    count_false=dict(count_false)#返回{键:数量值}
    print(f"count_true:{count_true}")
    print(f"count_false:{count_false}")
    return count_true,count_false

def json_load2dataframe(dirname,filename,typename=1):
    """
    功能：由路径加载json，并统计括号内元素个数，并返回DataFrame,饼状图
    形式变化：json->[{}]->Counter({})->DataFrame,饼状图
    要求：[{"有()"}]
    调用1(多个函数)：
    dirname='data\list_dict_file'
    filename='eval_test.txt.json_forward_model_last.mdl.json'
    # filename='eval_test.txt.json_backward_model_last.mdl.json'#302345 -[{}]
    #json_load2dataframe(dirname,filename)#有括号-括号中为类别
    json_load2dataframe(dirname,filename,typename=0)#括号中不是类别
    """
    ld=json_load_path(dirname,filename)
    count_true, count_false = list_dict_count(ld,typename=typename)
    print(f"count_true:{count_true}")
    print(f"count_false:{count_false}")
    count_true = dict(count_true.most_common(10))  # Counter取前n项-返回[(键,数量值),]
    count_false = dict(count_false.most_common(10))
    df_true=dict2DataFrame(count_true,'count_true')
    df_false=dict2DataFrame(count_false,'count_false')
    return df_true,df_false


class Json2GraphDict:
    """功能：由joson文件构建链接图（本质为字典：{实体id：相连实体id集合}）"""
    def __init__(self, train_path: str):
        logger.info('Start to build link graph from {}'.format(train_path))
        # id -> set(id)
        self.graph = {}
        examples = json.load(open(train_path, 'r', encoding='utf-8'))
        for ex in examples:
            head_id, tail_id = ex['head_id'], ex['tail_id']
            if head_id not in self.graph:
                self.graph[head_id] = set()
            self.graph[head_id].add(tail_id)
            if tail_id not in self.graph:
                self.graph[tail_id] = set()
            self.graph[tail_id].add(head_id)
        logger.info('Done build link graph with {} nodes'.format(len(self.graph)))

    def get_neighbor_ids(self, entity_id: str, max_to_keep=10) -> List[str]:
        # 获取指定实体 ID 的邻居实体 ID-有序
        neighbor_ids = self.graph.get(entity_id, set())
        return sorted(list(neighbor_ids))[:max_to_keep]

    def get_n_hop_entity_indices(self, entity_id: str,
                                 entity_dict: EntityDict,
                                 n_hop: int = 2,
                                 # return empty if exceeds this number
                                 max_nodes: int = 100000) -> set:
        """获取从指定实体 ID 出发的 N 跳邻居实体 ID"""
        if n_hop < 0:
            return set()

        seen_eids = set()
        seen_eids.add(entity_id)
        queue = deque([entity_id])
        for i in range(n_hop):
            len_q = len(queue)
            for _ in range(len_q):
                tp = queue.popleft()
                for node in self.graph.get(tp, set()):
                    if node not in seen_eids:
                        queue.append(node)
                        seen_eids.add(node)
                        if len(seen_eids) > max_nodes:
                            return set()
        return set([entity_dict.entity_to_idx(e_id) for e_id in seen_eids])